package net.danielthompson.danray.cameras;

import net.danielthompson.danray.cameras.apertures.Aperture;
import net.danielthompson.danray.structures.Constants;
import net.danielthompson.danray.structures.Point;
import net.danielthompson.danray.structures.Vector;

/**
 * Created by daniel on 2/27/14.
 */
public class DepthOfFieldCamera implements Camera {
   /**
    * Width of the image in pixels.
    */
   private int _width;

   /**
    * Height of the image in pixels.
    */
   private int _height;

   private Point xDir = new Point(1, 0, 0);
   private Point yDir = new Point(0, 1, 0);
   private Point zDir = new Point(0, 0, 1);

   private double _rotation;

   private int _focalLength;
   private double _zoomFactor;
   private double _focusDistance;
   private Vector _orientation;
   private Point _rearFocalPoint;

   private Aperture _aperture1;

   private Point _implicitDirection = new Point(0, 0, -1);

   /**
    * Creates a new Camera capable of depth-of field.
    * @param X Width of the image in pixels.
    * @param Y Height of the image in pixels.
    * @param focalLength Desired focal length.
    * @param orientation The location of the center of the image and the direction it's pointing.
    */
   public DepthOfFieldCamera(int X, int Y, int focalLength, double rotation, double zoomFactor, double focusDistance, Aperture aperture, Vector orientation) {

      _width = X;
      _height = Y;
      _focalLength = focalLength;
      _rotation = rotation;
      _zoomFactor = zoomFactor;
      _focusDistance = focusDistance;
      _aperture1 = aperture;
      _orientation = orientation;

      _rearFocalPoint = Point.Scale(orientation.Direction, -_focalLength);
      _rearFocalPoint.Plus(orientation.Origin);
   }

   @Override
   public Point GetWorldPointForPixel(int x, int y) {
      return GetWorldPointForPixel((double)x, (double)y);
   }

   @Override
   public Point GetWorldPointForPixel(double x, double y) {

      Point point = GetDefaultOrientationWorldPointForPixel(x, y);

      ConvertToWorldCoordinates(point);

      return point;
   }

   @Override
   public Vector GetStochasticRayForPixel(double x, double y) {
      Point centerPoint = GetStochasticWorldPointForPixel(x, y);
      Point direction = Point.Minus(centerPoint, _rearFocalPoint);
      direction.Normalize();
      direction.Scale(_focusDistance);
      Point focalPoint = Point.Plus(centerPoint, direction);
      return new Vector(centerPoint, Point.Minus(focalPoint, centerPoint));
   }

   @Override
   public Vector[] GetInitialStochasticRaysForPixel(double x, double y, int SamplesPerPixel) {

      Vector[] vectors = new Vector[SamplesPerPixel];

      if (SamplesPerPixel == 1) {
         vectors[0] = GetStochasticRayForPixel(x, y);
      }
      else {
         for (int i = 0; i < SamplesPerPixel; i++) {

            Point centerPoint = GetStochasticWorldPointForPixel(x, y);
            Point direction = Point.Minus(centerPoint, _rearFocalPoint);
            direction.Normalize();
            direction.Scale(_focusDistance);
            Point focalPoint = Point.Plus(centerPoint, direction);
            Point origin = GetStochasticWorldPointInApertureForPixel(x, y);
            Point direction2 = Point.Minus(focalPoint, origin);
            vectors[i] = new Vector(origin, direction2);
         }
      }
      return vectors;
   }

   @Override
   public Vector[] GetInitialStochasticRaysForPixel(int x, int y, int SamplesPerPixel) {
      return GetInitialStochasticRaysForPixel((double)x, (double)y, SamplesPerPixel);
   }

   private Point GetDefaultOrientationWorldPointForPixel(double x, double y) {
      double Cx = ( _orientation.Origin.getX() + _zoomFactor * (x - (double)_width * .5));
      double Cy = ( _orientation.Origin.getY() + _zoomFactor * ((double)_height * .5 - y));
      double Cz = _orientation.Origin.getZ();

      return new Point(Cx, Cy, Cz);
   }

   public Point GetStochasticWorldPointInApertureForPixel(double x, double y) {
      Point point = GetDefaultOrientationWorldPointForPixel(x, y);

      Point originPoint = _aperture1.GetOriginPoint();
      point.Plus(originPoint);

      ConvertToWorldCoordinates(point);

      return point;
   }

   public Point GetStochasticWorldPointForPixel(double x, double y) {
      Point point = GetDefaultOrientationWorldPointForPixel(x, y);

      double xOffset = (Math.random() - .5);
      double yOffset = (Math.random() - .5);

      point = new Point(point.getX() + xOffset, point.getY() + yOffset, point.getZ());

      ConvertToWorldCoordinates(point);

      return point;

   }

   public void ConvertToWorldCoordinates(Point point) {
      double dot = _orientation.Direction.Dot(_implicitDirection);

      if (!Constants.WithinDelta(dot, 1))
      {
         Point rotationDirection = _orientation.Direction.Cross(zDir);
         rotationDirection.Normalize();
         Vector rotationAxis = new Vector(_orientation.Origin, rotationDirection);

         double theta = Math.toDegrees(Math.acos(_orientation.Direction.Dot(_implicitDirection)));

         point.Rotate(rotationAxis, theta);
         point.Rotate(_orientation, _rotation);
      }

   }






}